﻿using System;
using System.Text;
using System.Collections.Generic;
using System.Linq;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.IO;
using Engine.Sgml.Schema;
using Engine.Sgml.Validation;
using System.Diagnostics;
using Engine.Sgml.Test.Util;
using Engine.Sgml.Matching;

namespace Engine.Sgml.Test {
    /// <summary>
    /// Summary description for ModelGroupsReconstructorFisture
    /// </summary>
    [TestClass]
    public class ModelGroupsReconstructorFixture {
        public ModelGroupsReconstructorFixture() {
            //
            // TODO: Add constructor logic here
            //
            }

        private TestContext testContextInstance;

        /// <summary>
        ///Gets or sets the test context which provides
        ///information about and functionality for the current test run.
        ///</summary>
        public TestContext TestContext {
            get {
                return testContextInstance;
                }
            set {
                testContextInstance = value;
                }
            }

        #region Additional test attributes
        //
        // You can use the following additional attributes as you write your tests:
        //
        // Use ClassInitialize to run code before running the first test in the class
        // [ClassInitialize()]
        // public static void MyClassInitialize(TestContext testContext) { }
        //
        // Use ClassCleanup to run code after all tests in a class have run
        // [ClassCleanup()]
        // public static void MyClassCleanup() { }
        //
        // Use TestInitialize to run code before running each test 
        [TestInitialize()]
        public void MyTestInitialize() {
            DirectoryInfo fileDirectory = Directory.GetParent(TestContext.TestDir).Parent;

            DirectoryInfo schemaDirectory = new DirectoryInfo(Path.Combine(fileDirectory.FullName, @"Engine.Sgml.Test\Scheme Samples"));
            DirectoryInfo saveDirectory = new DirectoryInfo(Path.Combine(fileDirectory.FullName, @"Engine.Sgml.Test\Saved Docs"));
            fileDirectory = new DirectoryInfo(Path.Combine(fileDirectory.FullName, @"Engine.Sgml.Test\Document Samples"));

            Assert.IsTrue(schemaDirectory.Exists);
            Assert.IsTrue(fileDirectory.Exists);
            Assert.IsTrue(saveDirectory.Exists);

            schemaDir = schemaDirectory.FullName;
            fileDir = fileDirectory.FullName;
            }
        //
        // Use TestCleanup to run code after each test has run
        // [TestCleanup()]
        // public void MyTestCleanup() { }
        //

        private static string schemaDir;
        private string fileDir;
        #endregion


        [TestMethod]
        public void ReconstructTest() {
            DElementConfiguration elementConfig = new DElementConfiguration();
            DModelGroupsConfiguration groupsConfig = new DModelGroupsConfiguration(elementConfig);
            groupsConfig.GenerateInclusionElements = true;
            DtdSchemaGenerator schemaGenerator = new DtdSchemaGenerator();
            SgmlDocumentGenerator generator = new SgmlDocumentGenerator(true);
            ModelMatcher firstMatcher = new ModelMatcher();
            ModelMatcher secondMatcher = new ModelMatcher();

            Random random = new Random();
            int totalTestCount = 100000;
            do {
                // Generate a model groups
                DtdSchema schema = null;
                do {
                    groupsConfig.MaxLength = random.Next(4, 7);
                    groupsConfig.MaxDepth = random.Next(1, 7);
                    elementConfig.ElementNumber = 1;
                    schema = GetSchema(groupsConfig, schemaGenerator);
                    } while (schema.ElementDeclarations.Count() < 15 || schema.ElementDeclarations.Count() > 35);

                // Generate a scheme file name and document file name
                string fileName = Path.GetRandomFileName();
                string schemaName = string.Format("{0}.dtd", fileName);
                fileName = String.Format("{0}.xml", fileName);
                if (Debug.Listeners.Count == 0)
                    Debug.Listeners.Add(new DefaultTraceListener());

                Debug.Print("Tested document file name: {0}", fileName);
                // Create a tested document with the generated scheme
                SgmlDocument secondDoc = new SgmlDocument(schema);
                Debug.Listeners.Clear();

                int totalSchemaTestCount = 5000;
                do {
                    // Generate a document based on the generated scheme
                    SgmlDocument firstDoc = null;
                    firstDoc = generator.Generate(schema.RootDeclaration);
                    string xmlBefore = firstDoc.ToString();
                    // Analyze the document
                    IList<Capture> firstMatch = firstMatcher.Match(firstDoc.Root.FirstNode());
                    // Load the file to a document
                    try {
                        secondDoc.Load(new StringReader(firstDoc.ToString()));
                        string xmlAfter = secondDoc.ToString();
                        }
                    catch (Exception) {
                        SaveSchema(schema, new Uri(schemaDir), schemaName);
                        SaveDocument(firstDoc, new Uri(fileDir), fileName);
                        throw;
                        }
                    // Analyze the document
                    IList<Capture> secondMatch = secondMatcher.Match(secondDoc.Root.FirstNode());
                    // Verify the match of model groups
                    foreach (Capture capture in from c in secondMatch
                                                where c.Success && c.Entity.IsModelMatch
                                                select c) {
                        if (capture.Entity.Match.Type == AccordanceTypes.Partial) {
                            SaveSchema(schema, new Uri(schemaDir), schemaName);
                            SaveDocument(firstDoc, new Uri(fileDir), fileName);
                            }
                        Assert.IsFalse(capture.Entity.Match.Type == AccordanceTypes.Partial);
                        }
                    } while (--totalSchemaTestCount > 0);
                //if (totalSchemaTestCount <= 0)
                //    break;
                } while (--totalTestCount > 0);
            }

        private static DtdSchema GetSchema(DModelGroupsConfiguration groupsConfig, DtdSchemaGenerator schemaGenerator) {
            //return DtdSchema.Load(new Uri(Path.Combine(schemaDir, "Resume.dtd")), "resume");
            //return DtdSchema.Load(new Uri(Path.Combine(schemaDir, "Anthology.dtd")), "ANTHOLOGY");
            return schemaGenerator.Generate(groupsConfig);
            }

        private void SaveSchema(DtdSchema schema, Uri baseUrl, string schemaName) {
            StringWriter writer = new StringWriter();
            foreach (DElementDeclaration declaration in schema.ElementDeclarations) {
                writer.Write("<!ELEMENT");
                writer.Write(' ');
                writer.Write(declaration.Name.ToString());
                writer.Write(' ');
                switch (declaration.ContentModel.ContentType) {
                    case DContentModelType.Default:
                        writer.Write(declaration.ContentModel.Groups.ToString());
                        if (declaration.ContentModel.HasInclusionExceptions()) {
                            writer.Write(' ');
                            writer.Write('+');
                            writer.Write('(');
                            DName name = declaration.ContentModel.Inclusions.First();
                            writer.Write(name.ToString());
                            for (int i = 1; i < declaration.ContentModel.Inclusions.Count(); i++) {
                                writer.Write('|');
                                name = declaration.ContentModel.Inclusions.ElementAt(i);
                                writer.Write(name.ToString());
                                }
                            writer.Write(')');
                            }
                        break;
                    case DContentModelType.CData:
                        writer.Write("CDATA");
                        break;
                    case DContentModelType.RCData:
                        break;
                    case DContentModelType.Empty:
                        writer.Write("EMPTY");
                        break;
                    default:
                        break;
                    }
                writer.Write('>');
                writer.WriteLine();
                }
            Uri address = new Uri(Path.Combine(baseUrl.LocalPath, schemaName));
            File.WriteAllText(address.LocalPath, writer.GetStringBuilder().ToString());
            writer.Close();
            }

        private void SaveDocument(SgmlDocument document, Uri baseUrl, string fileName) {
            StringWriter writer = new StringWriter();
            document.Save(writer);
            Uri address = new Uri(Path.Combine(baseUrl.LocalPath, fileName));
            File.WriteAllText(address.LocalPath, writer.GetStringBuilder().ToString());
            writer.Close();
            }
        }
    }
